# minio_client.py
import logging
import os
from datetime import timedelta
from io import BytesIO
from typing import Optional

from minio import Minio
from minio.commonconfig import CopySource
from minio.error import S3Error, InvalidResponseError

from app.config import settings

logger = logging.getLogger(__name__)


class MinioClient:
    def __init__(self,bucket_name: str = None):
        self.client = Minio(
            endpoint=settings.MINIO_ENDPOINT,
            access_key=settings.MINIO_ACCESS_KEY,
            secret_key=settings.MINIO_SECRET_KEY,
            secure=settings.MINIO_SECURE
        )
        if bucket_name is not None:
            self.bucket_name = bucket_name
        else:
            self.bucket_name = settings.MINIO_BUCKET
        self._ensure_bucket_exists()

    def _ensure_bucket_exists(self):
       """确保存储桶存在，不存在则创建"""
       return self._ensure_custom_bucket(self.bucket_name)

    def _ensure_custom_bucket(self,bucket_name):
        """确保存储桶存在，不存在则创建"""
        if not self.client.bucket_exists(bucket_name):
            try:
                self.client.make_bucket(bucket_name)
                logger.info(f"创建MinIO存储桶: {bucket_name}")
                return True
            except S3Error as e:
                logger.error(f"创建MinIO存储桶失败: {e}")
                raise

    def _get_content_type(self, file_name: str) -> str:
        """根据文件名获取MIME类型"""
        ext = os.path.splitext(file_name)[1].lower()
        content_types = {
            '.txt': 'text/plain',
            '.csv': 'text/csv',
            '.json': 'application/json',
            '.pdf': 'application/pdf',
            '.jpg': 'image/jpeg',
            '.jpeg': 'image/jpeg',
            '.png': 'image/png',
            '.zip': 'application/zip',
            '.doc': 'application/msword',
            '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            '.xls': 'application/vnd.ms-excel',
            '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        }
        return content_types.get(ext, 'application/octet-stream')

    def upload_file(self, file_object, original_name: str, minio_object_name: Optional[str] = None) -> Optional[str]:
        """
        上传文件到MinIO
        :param file_object: 文件对象
        :param original_name: 原始文件名
        :param minio_object_name: MinIO中的存储名称
        :return: 存储的对象名称
        """
        try:
            # 处理文件内容
            if hasattr(file_object, 'read'):
                file_content = file_object.read()
                file_size = len(file_content)
                file_object.seek(0)  # 重置文件指针
            else:
                file_content = file_object
                file_size = len(file_content)


            # 2. 将字节流转换为可读取的类文件对象（关键修复）
            data_stream = BytesIO(file_content)

            # 3. 确定MinIO中的存储名称
            object_name = minio_object_name if minio_object_name else original_name

            # 上传到MinIO
            self.client.put_object(
                bucket_name=self.bucket_name,
                object_name=object_name,
                data=data_stream,
                length=file_size,
                content_type=self._get_content_type(original_name)
            )

            logger.info(f"文件上传成功: {self.bucket_name}/{object_name}")
            return object_name
        except (S3Error, InvalidResponseError) as e:
            logger.error(f"MinIO上传失败: {str(e)}")
            raise
        except Exception as e:
            logger.error(f"文件处理错误: {str(e)}")
            raise

    def upload_file_for_bucket(self, file_object, original_name: str, bucket_name:str,minio_object_name: Optional[str] = None) -> Optional[str]:
        """
        上传文件到MinIO
        :param file_object: 文件对象
        :param original_name: 原始文件名
        :param minio_object_name: MinIO中的存储名称
        :return: 存储的对象名称
        """
        try:
            # 处理文件内容
            if hasattr(file_object, 'read'):
                file_content = file_object.read()
                file_size = len(file_content)
                file_object.seek(0)  # 重置文件指针
            else:
                file_content = file_object
                file_size = len(file_content)


            # 2. 将字节流转换为可读取的类文件对象（关键修复）
            data_stream = BytesIO(file_content)

            # 3. 确定MinIO中的存储名称
            object_name = minio_object_name if minio_object_name else original_name
            self._ensure_custom_bucket(bucket_name)
            # 上传到MinIO
            self.client.put_object(
                bucket_name=bucket_name,
                object_name=object_name,
                data=data_stream,
                length=file_size,
                content_type=self._get_content_type(original_name)
            )

            logger.info(f"文件上传成功: {bucket_name}/{object_name}")
            return object_name
        except (S3Error, InvalidResponseError) as e:
            logger.error(f"MinIO上传失败: {str(e)}")
            raise
        except Exception as e:
            logger.error(f"文件处理错误: {str(e)}")
            raise
    def get_presigned_url(self, object_name: str, expires_in: timedelta) -> str:
        """获取带签名的访问URL"""
        try:
            return self.client.presigned_get_object(
                bucket_name=self.bucket_name,
                object_name=object_name,
                expires=expires_in
            )
        except S3Error as e:
            logger.error(f"生成预签名URL失败: {str(e)}")
            raise e

    def get_presigned_url_for_bucket(self, object_name: str, expires_in: timedelta,bucket_name:str) -> str:
        """获取带签名的访问URL"""
        try:
            return self.client.presigned_get_object(
                bucket_name=bucket_name,
                object_name=object_name,
                expires=expires_in
            )
        except S3Error as e:
            logger.error(f"生成预签名URL失败: {str(e)}")
            raise e

    def get_object(self, object_name: str):
        """获取带签名的访问URL"""
        return self.get_object_bucket(self.bucket_name,object_name)

    def get_object_bucket(self, bucket_name,object_name: str):
        """获取带签名的访问URL"""
        try:
            return self.client.get_object(
                bucket_name=bucket_name,
                object_name=object_name
            )
        except S3Error as e:
            logger.error(f"生成预签名URL失败: {str(e)}")
            raise e

    def download_file(self, object_name: str, file_path: str) -> str:
        """从MinIO下载文件"""
        try:
            self.client.fget_object(
                bucket_name=self.bucket_name,
                object_name=object_name,
                file_path=file_path
            )
            return file_path
        except S3Error as e:
            logger.error(f"MinIO下载失败: {str(e)}")
            raise e
    #删除对应桶内的对应的文件
    def delete_bucket_object(self, bucket_name: str, object_name: str):
        """删除MinIO中的文件"""
        try:
            self.client.remove_object(
                bucket_name=bucket_name,
                object_name=object_name
            )
            logger.info(f"文件删除成功: {bucket_name}/{object_name}")

        except S3Error as e:
            logger.error(f"MinIO删除失败: {str(e)}")
            raise e
    #获取桶内的所有对象
    def get_bucket_objects(self, bucket_name: str) -> list[str]:
        """获取MinIO中的所有文件"""
        try:
            objects = self.client.list_objects(
                bucket_name=bucket_name
            )
            return [obj.object_name for obj in objects]
        except S3Error as e:
            logger.error(f"MinIO获取失败: {str(e)}")
            raise e
    #转移桶的对象指定的回收桶,实现先复制再删除的设计
    def move_bucket_object(self,source_bucket: str,source_object: str):
        """转移MinIO中的文件"""

        try:
            # 确保目标桶存在
            logger.info(f"检查目标桶是否存在: {source_bucket}")
            self._ensure_custom_bucket(source_bucket)

            # 创建源对象的CopySource
            source = CopySource(
                bucket_name=source_bucket,
                object_name=source_object
            )
            logger.info(f"创建源对象CopySource成功")

            # 执行复制操作（转移）
            logger.info(f"开始复制对象")
            trash_bucket_name = f"{source_bucket}trash"
            self.client.copy_object(
                bucket_name=trash_bucket_name,  # 目标桶名
                object_name=source_object,  # 目标对象名
                source=source  # 源对象信息
            )
            logger.info(f"复制对象完成")

            # 复制成功后再删除源对象
            logger.info(f"开始删除源对象: {source_bucket}/{source_object}")
            self.client.remove_object(
                bucket_name=source_bucket,
                object_name=source_object
            )
            logger.info(f"文件转移成功: {source_bucket}/{source_object} -> {trash_bucket_name}/{source_object}")

        except S3Error as e:
            logger.error(f"MinIO转移失败: {str(e)}")
            logger.error(f"错误详情: {e.code}, {e.message}")
            raise e
        except Exception as e:
            logger.error(f"未知错误: {str(e)}", exc_info=True)
            raise e
